package no.netlight.gotogate;

import java.util.ArrayList;
import java.util.Arrays;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.Traverser;
import org.neo4j.index.IndexService;
import org.neo4j.index.lucene.LuceneIndexService;
import org.neo4j.kernel.EmbeddedGraphDatabase;

public class GraphConstructor {

	private static int count = 0;
	private GraphDatabaseService db;
	private IndexService index;
	private Node root;

	/**
	 * 
	 */
	public GraphConstructor(String flyplass) {
		count++;
		db = new EmbeddedGraphDatabase("var/gotogateDB1"+flyplass+count);
		
		Transaction txi = db.beginTx();
		try {
			
			index = new LuceneIndexService(db);
			
			txi.success();
		} catch (Exception e) {
			txi.failure();
		} finally {
			txi.finish();
		}
	}
	public boolean DatabaseIsEmpty(String flyplass){
		Transaction txi = db.beginTx();
		Node s = null;
		try {
			s = index.getSingleNode("name", flyplass);
			txi.success();
		} catch (Exception e) {
			txi.failure();
		} finally {
			txi.finish();
		}
			
		//At the moment, if the airport has been created (a database has been created before) then we do nothing with the database 
		return s == null ? true:false;
	}
	
	public IndexService getIndex() {
		return index;
	}

	public Transaction beginTx() {
		return db.beginTx();
	}

	public boolean stopExists(String name) {
		Transaction tx = this.beginTx();
		try {
			boolean result = index.getSingleNode("name", name) == null ? false
					: true;
			tx.success();
			tx.finish();
			return result;
		} catch (Exception e) {
			tx.failure();
			tx.finish();
			return false;
		}
	}

	public boolean relationExists(String relName) {
		Transaction tx = this.beginTx();

		try {
			for (Node root : db.getAllNodes()) {
				Traverser trav = root.traverse(Traverser.Order.BREADTH_FIRST,
						StopEvaluator.DEPTH_ONE, ReturnableEvaluator.ALL,
						MyRelationshipTypes.GOESTO, Direction.OUTGOING);
				for (Node node : trav) {
					Iterable<Relationship> relationships = node
							.getRelationships();
					for (Relationship rel : relationships) {
						if (rel.getProperty("name").equals(relName)) {
							tx.success();
							tx.finish();
							return true;
						}
					}
				}
			}
			tx.success();
			tx.finish();
			return false;
		} catch (Exception e) {
			tx.failure();
			tx.finish();
			return false;
		}
	}

	public Relationship getRelation(String relName) {
		Transaction tx = this.beginTx();

		try {
			for (Node root : db.getAllNodes()) {
				Traverser trav = root.traverse(Traverser.Order.BREADTH_FIRST,
						StopEvaluator.DEPTH_ONE, ReturnableEvaluator.ALL,
						MyRelationshipTypes.GOESTO, Direction.OUTGOING);
				for (Node node : trav) {
					Iterable<Relationship> relationships = node
							.getRelationships();
					for (Relationship rel : relationships) {
						if (rel.getProperty("name").equals(relName)) {
							tx.success();
							tx.finish();
							return rel;
						}
					}
				}
			}
			tx.success();
			tx.finish();
			return null;
		} catch (Exception e) {
			tx.failure();
			tx.finish();
			return null;
		}
	}

	/**
	 * 
	 * 
	 * 
	 * @param routeTable
	 *            The format of the routetable should be,
	 *            {"name of target node", "name of the from node",
	 *            "type of transport", [every element in the list after this
	 *            point should be pairs of strings formatted for the regex
	 *            "[0-9a-zA-Z]" with alternating departure and arival times
	 */
	public void generateGraphFromRouteTable(ArrayList<String[]> routeTable) {
		if(routeTable.size() < 1){
			return;
		}
		
		//This is a hack... don't keep this. If the first stop of the route table exists, the database will not be created!
		if(this.stopExists(routeTable.get(0)[0])){
			return;
		}
		
		for (String[] routesegment : routeTable) {
			//if the route segment is to short to contain anything of value, or has deptimes with no arrtimes, we should just ignore the entire segment 
			if(routesegment.length < 5 && routesegment.length%2 == 1){
				continue;
			}
			
			ArrayList<String> departureTimes = new ArrayList<String>();
			ArrayList<String> arivalTimes = new ArrayList<String>();
			
			for (int i = 3; i < routesegment.length; i+=2) {
				departureTimes.add(routesegment[i]);
				
				if(i+1 < routesegment.length){
					arivalTimes.add(routesegment[i+1]);
				}
			}
			
			//Neo4j does not support anything other than primitive datatypes + String and array variants of these. ArrayList is not supported and there can be no null elements in arrays stored in database objects 
			String[] depTimes = departureTimes.toArray(new String[departureTimes.size()]);
			String[] arrTimes = arivalTimes.toArray(new String[arivalTimes.size()]);

			Node target = this.setNode(routesegment, true);
			Node source = this.setNode(routesegment, false);

			this.setRelation(target, source, routesegment[2], arrTimes,
					depTimes);
		}
	}

	private void setRelation(Node target, Node source, String transportLineName,
			String[] arivalTimes, String[] departureTimes) {
		transportLineName = transportLineName.trim();
		Transaction txRel = db.beginTx();
		try {
			Iterable<Relationship> relationships = source
					.getRelationships(Direction.OUTGOING);
			boolean relationExists = false;
			Relationship relationship = null;
			
			for (Relationship rel : relationships) {
				if (rel.getProperty("name").equals(transportLineName) && rel.getEndNode().getProperty("name").equals(target.getProperty("name"))) {
					relationExists = true;
					relationship = rel;
				}
			}

			if (!relationExists) {
				relationship = source.createRelationshipTo(target,
						MyRelationshipTypes.GOESTO);
				relationship.setProperty("name", transportLineName);
				relationship.setProperty("arivaltimes", arivalTimes);
				relationship.setProperty("departuretimes", departureTimes);
				
			}else{
				ArrayList<String> arrtimes = new ArrayList<String>(Arrays.asList((String[]) relationship.getProperty("arrivaltimes")));
				for(String time: departureTimes){
					arrtimes.add(time);
				}
				relationship.setProperty("arrivaltimes", arrtimes.toArray(new String[arrtimes.size()]));
				
				ArrayList<String> deptimes = new ArrayList<String>(Arrays.asList((String[]) relationship.getProperty("departuretiems")));
				for(String time: departureTimes){
					deptimes.add(time);
				}
				relationship.setProperty("departuretimes", deptimes.toArray(new String[deptimes.size()]));
			}
			txRel.success();
		}catch (Exception e){
			txRel.failure();
		}finally {
			txRel.finish();
		}
	}

	private Node setNode(String[] routeSegment, boolean isTargetNode) {
		Node currentNode;
		Transaction txCurrent = db.beginTx();
		try {
			String nodeName;
			if (isTargetNode) {
				nodeName = routeSegment[1].trim();
			} else {
				nodeName = routeSegment[0].trim();
			}
			currentNode = index.getSingleNode("name", nodeName);

			if (currentNode == null) {
				currentNode = db.createNode();

				String[] typeOfTransport = new String[1];
				typeOfTransport[0] = routeSegment[2].trim();

				currentNode.setProperty("name", nodeName);
				currentNode.setProperty("typeoftransport", typeOfTransport);

				index.index(currentNode, "name", nodeName);
			} else {
				String[] type = (String[]) currentNode
						.getProperty("typeoftransport");
				boolean transportExists = false;
				for (String transport : type) {
					if (transport.equals(routeSegment[2])) {
						transportExists = true;
					}
				}
				if (!transportExists) {
					String[] tmp = new String[type.length + 1];

					System.arraycopy(type, 0, tmp, 0, type.length);
					tmp[type.length] = routeSegment[2].trim();
					currentNode.setProperty("typeoftransport", tmp);
				}
			}
			txCurrent.success();
		} finally {
			txCurrent.finish();
		}
		return currentNode;
	}

	public String printDB(String stopName) {
		String out = "";
		Transaction tx = db.beginTx();

		try {
			root = index.getSingleNode("name", stopName);

			Traverser trav = root.traverse(Traverser.Order.BREADTH_FIRST,
					StopEvaluator.END_OF_GRAPH, ReturnableEvaluator.ALL,
					MyRelationshipTypes.GOESTO, Direction.OUTGOING);
			for (Node node : trav) {
				out += "From " + node.getProperty("name");

				Iterable<Relationship> relationships = node.getRelationships();
				for (Relationship rel : relationships) {
					if (rel.getStartNode().getProperty("name") != rel
							.getEndNode().getProperty("name")
							&& rel.getEndNode().getProperty("name") != node
									.getProperty("name")) {
						out += rel.getProperty("name") + " " + rel.getType()
								+ " " + rel.getEndNode().getProperty("name");
						String[] depTimes = (String[]) rel.getProperty("departuretimes");
						String[] arTimes = (String[]) rel.getProperty("arivaltimes");
						for(int i = 0; i < depTimes.length; i++ ){		
							out += " " + depTimes[i] + ":" +  arTimes[i];
						}
						out += "\n";
					}
				}
			}
			tx.success();
		} catch(Exception e){
			System.out.println("failed to print");
			tx.failure();
		}finally {
			tx.finish();
		}
		return out;
	}

	public void shutdown() {
		db.shutdown();
	}
}
